import pandas as pd
import numpy as np
import matplotlib
# 设置matplotlib使用非交互式后端，避免tkinter相关错误
matplotlib.use('Agg')
from statsmodels.tsa.arima.model import ARIMA
from prophet import Prophet
import matplotlib.pyplot as plt
from sklearn.metrics import mean_absolute_error, mean_squared_error
from matplotlib.font_manager import FontProperties

# 设置中文字体
try:
    # font = FontProperties(fname=r"C:\Windows\Fonts\simhei.ttf")
    # font = FontProperties(fname=r"font/SimHei.ttf")
    # font = FontProperties(fname="~/Library/Fonts/SimHei.ttf")
    # font = FontProperties(fname="font/SimHei.ttf")
    # # plt.rcParams['font.sans-serif'] = ['SimHei']
    # plt.rcParams['font.sans-serif'] = ['PingFang SC']
    # font_path = "font/SimHei.ttf"  # macOS 系统路径
    # font = FontProperties(fname=font_path)
    # plt.rcParams['axes.unicode_minus'] = False  # 解决负号'-'显示为方块的问题

    # 设置全局字体（确保字体文件存在）
    # 设置 Mac 专用字体（优先级从高到低）
    plt.rcParams['font.family'] = 'sans-serif'
    plt.rcParams['font.sans-serif'] = [
        'PingFang SC',  # 苹方-简（macOS 内置）
        'Heiti SC',  # 黑体-简（macOS 内置）
        'Arial Unicode MS',  # 跨平台备选
        'Hiragino Sans GB'  # 冬青黑体（部分 Mac 安装）
    ]
    plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

    # 显式指定字体（备用）
    # font_path = "/System/Library/Fonts/PingFang.ttc"  # Mac 路径
    font_path = "font/SimHei.ttf"  # 自定义路径（需确保文件存在）
    font = FontProperties(fname=font_path)
except:
    pass

class TimeSeriesModel:
    def __init__(self, data=None):
        """
        初始化时间序列模型
        
        参数:
            data: 可以是以下格式之一:
                - 带有日期索引和 sales_quantity 列的 DataFrame
                - 带有日期列和 sales_quantity 列的 DataFrame
                - 带有日期索引和任意数值列的 DataFrame（第一个数值列将被视为 sales_quantity）
        """
        if data is not None:
            self.data = self._prepare_data(data)
        else:
            self.data = None
        self.model = None
        self.predictions = None
        self.model_fit = None
        self.prophet_model = None
        self.prophet_predictions = None
        
    def _prepare_data(self, data):
        """确保数据格式正确，包含日期索引和 sales_quantity 列"""
        df = data.copy()
        
        # 检查是否有日期索引
        if not isinstance(df.index, pd.DatetimeIndex):
            # 检查是否有名为'date'的列
            if 'date' in df.columns:
                df['date'] = pd.to_datetime(df['date'])
                df = df.set_index('date')
            else:
                # 尝试查找可能的日期列
                for col in df.columns:
                    if 'date' in col.lower() or 'time' in col.lower():
                        try:
                            df[col] = pd.to_datetime(df[col])
                            df = df.set_index(col)
                            break
                        except:
                            continue
        
        # 检查是否有 sales_quantity 列
        if 'sales_quantity' not in df.columns:
            # 如果没有 sales_quantity 列，但数据是时间序列（只有一列数值数据）
            numeric_cols = df.select_dtypes(include=['number']).columns
            if len(numeric_cols) == 1:
                # 如果只有一个数值列，将其重命名为 sales_quantity
                df = df.rename(columns={numeric_cols[0]: 'sales_quantity'})
            elif len(numeric_cols) > 1:
                # 如果有多个数值列，使用第一个作为 sales_quantity
                df['sales_quantity'] = df[numeric_cols[0]]
            else:
                raise ValueError("无法找到合适的数值列作为销售数量")
        
        # 最终检查
        if not isinstance(df.index, pd.DatetimeIndex):
            raise ValueError("无法处理数据：需要日期索引")
        if 'sales_quantity' not in df.columns:
            raise ValueError("无法处理数据：需要 sales_quantity 列")
            
        return df
        
    def fit_arima(self, data=None, order=(1, 1, 1)):
        """拟合ARIMA模型"""
        if data is not None:
            self.data = data
            
        # 确保数据是时间序列格式
        if not isinstance(self.data.index, pd.DatetimeIndex):
            raise ValueError("数据索引必须是日期时间类型")
            
        # 拟合ARIMA模型
        self.model = ARIMA(self.data['sales_quantity'], order=order)
        self.model_fit = self.model.fit()
        
        return self.model_fit
    
    def predict_arima(self, steps=30):
        """使用ARIMA模型进行预测"""
        if self.model_fit is None:
            raise ValueError("必须先拟合模型")
            
        # 进行预测
        forecast = self.model_fit.forecast(steps=steps)
        
        # 创建预测日期范围
        last_date = self.data.index[-1]
        forecast_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=steps)
        
        # 创建预测DataFrame
        self.predictions = pd.DataFrame({
            'date': forecast_dates,
            'sales_quantity_pred': forecast
        })
        self.predictions = self.predictions.set_index('date')
        
        return self.predictions
    
    def fit_prophet(self, data=None):
        """拟合Prophet模型"""
        if data is not None:
            self.data = data
        
        # 确保数据不为空
        if self.data is None or len(self.data) == 0:
            raise ValueError("数据不能为空")
            
        # 确保有sales_quantity列
        if 'sales_quantity' not in self.data.columns:
            raise ValueError("数据必须包含'sales_quantity'列")
        
        # 准备Prophet所需的数据格式
        try:
            # 确保数据有索引, 如果索引不是日期, 检查是否有date列
            if not isinstance(self.data.index, pd.DatetimeIndex):
                if 'date' in self.data.columns:
                    prophet_data = self.data.copy()
                else:
                    raise ValueError("数据必须有日期索引或'date'列")
            else:
                # 如果索引是日期, 重置索引将其变为列
                prophet_data = self.data.reset_index()
                # 确保date列存在
                if 'date' not in prophet_data.columns:
                    prophet_data = prophet_data.rename(columns={prophet_data.columns[0]: 'date'})
            
            # 检查并重命名列以符合Prophet要求
            date_col = 'date'
            quantity_col = 'sales_quantity'
            
            # 重命名为Prophet需要的格式
            column_mapping = {
                date_col: 'ds',
                quantity_col: 'y'
            }
            
            # 应用重命名
            prophet_data = prophet_data.rename(columns=column_mapping)
            
            # 确保ds和y列存在
            if 'ds' not in prophet_data.columns or 'y' not in prophet_data.columns:
                raise ValueError(f"重命名后数据缺少必要的列。当前列: {prophet_data.columns.tolist()}")
            
            # 确保ds是日期时间类型
            prophet_data['ds'] = pd.to_datetime(prophet_data['ds'])
            
            # 确保y是数值类型
            prophet_data['y'] = pd.to_numeric(prophet_data['y'], errors='coerce')
            prophet_data = prophet_data.dropna(subset=['y'])
            
            # 创建并拟合模型
            self.prophet_model = Prophet(
                yearly_seasonality=True,
                weekly_seasonality=True,
                daily_seasonality=False,
                seasonality_mode='multiplicative'
            )
            
            # 添加节假日效应（可选）
            # self.prophet_model.add_country_holidays(country_name='CN')
            
            # 添加自定义季节性
            self.prophet_model.add_seasonality(name='monthly', period=30.5, fourier_order=5)
            
            # 拟合模型 - 仅使用必要的列
            prophet_data_subset = prophet_data[['ds', 'y']].copy()
            
            # 检查数据是否有足够的行
            if len(prophet_data_subset) < 2:
                raise ValueError(f"数据点数量不足，Prophet至少需要2个数据点，当前只有{len(prophet_data_subset)}个")
                
            # 检查y是否有变化
            if prophet_data_subset['y'].nunique() <= 1:
                raise ValueError("销售数据没有变化，无法进行有效的预测")
                
            print(f"拟合Prophet模型，数据形状: {prophet_data_subset.shape}，列：{prophet_data_subset.columns.tolist()}")
            print(f"数据样例: \n{prophet_data_subset.head()}")
            
            self.prophet_model.fit(prophet_data_subset)
            
            return self.prophet_model
            
        except Exception as e:
            raise ValueError(f"准备Prophet数据时出错: {str(e)}")
    
    def predict_prophet(self, periods=30):
        """使用Prophet模型进行预测"""
        if self.prophet_model is None:
            raise ValueError("必须先拟合Prophet模型")
            
        # 创建未来日期DataFrame
        future = self.prophet_model.make_future_dataframe(periods=periods)
        
        # 进行预测
        forecast = self.prophet_model.predict(future)
        
        # 存储预测结果
        self.prophet_predictions = forecast
        
        return forecast
    
    def plot_prophet_forecast(self, title='盲盒销售预测', save_path=None):
        """绘制Prophet预测结果图"""
        if self.prophet_model is None or self.prophet_predictions is None:
            raise ValueError("必须先拟合模型并进行预测")
            
        fig = self.prophet_model.plot(self.prophet_predictions)
        plt.title(title)
        plt.xlabel('日期')
        plt.ylabel('销售数量')
        
        if save_path:
            plt.savefig(save_path)
        
        # 关闭图形，避免内存泄漏和tkinter错误
        result_fig = fig
        plt.close(fig)
            
        return result_fig
    
    def evaluate_model(self, test_data):
        """评估模型性能"""
        if self.predictions is None:
            raise ValueError("必须先进行预测")
            
        # 计算评估指标
        mae = mean_absolute_error(test_data['sales_quantity'], self.predictions['sales_quantity_pred'])
        rmse = np.sqrt(mean_squared_error(test_data['sales_quantity'], self.predictions['sales_quantity_pred']))
        
        return {
            'MAE': mae,
            'RMSE': rmse
        }
